package org.tartarus.snowball;

import java.lang.reflect.InvocationTargetException;

public class SnowballProgram {

    protected SnowballProgram() {
        current = new StringBuffer();
        setCurrent("");
    }

    /**
     * Set the current string.
     */
    public void setCurrent(String value) {
        current.replace(0, current.length(), value);
        cursor = 0;
        limit = current.length();
        limit_backward = 0;
        bra = cursor;
        ket = limit;
    }

    /**
     * Get the current string.
     */
    public String getCurrent() {
        String result = current.toString();
        // Make a new StringBuffer.  If we reuse the old one, and a user of
        // the library keeps a reference to the buffer returned (for example,
        // by converting it to a String in a way which doesn't force a copy),
        // the buffer size will not decrease, and we will risk wasting a large
        // amount of memory.
        // Thanks to Wolfram Esser for spotting this problem.
        current = new StringBuffer();
        return result;
    }
    // current string
    protected StringBuffer current;
    protected int cursor;
    protected int limit;
    protected int limit_backward;
    protected int bra;
    protected int ket;

    protected void copy_from(SnowballProgram other) {
        current = other.current;
        cursor = other.cursor;
        limit = other.limit;
        limit_backward = other.limit_backward;
        bra = other.bra;
        ket = other.ket;
    }

    protected boolean in_grouping(char[] s, int min, int max) {
        if (cursor >= limit) {
            return false;
        }
        char ch = current.charAt(cursor);
        if (ch > max || ch < min) {
            return false;
        }
        ch -= min;
        if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) {
            return false;
        }
        cursor++;
        return true;
    }

    protected boolean in_grouping_b(char[] s, int min, int max) {
        if (cursor <= limit_backward) {
            return false;
        }
        char ch = current.charAt(cursor - 1);
        if (ch > max || ch < min) {
            return false;
        }
        ch -= min;
        if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) {
            return false;
        }
        cursor--;
        return true;
    }

    protected boolean out_grouping(char[] s, int min, int max) {
        if (cursor >= limit) {
            return false;
        }
        char ch = current.charAt(cursor);
        if (ch > max || ch < min) {
            cursor++;
            return true;
        }
        ch -= min;
        if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) {
            cursor++;
            return true;
        }
        return false;
    }

    protected boolean out_grouping_b(char[] s, int min, int max) {
        if (cursor <= limit_backward) {
            return false;
        }
        char ch = current.charAt(cursor - 1);
        if (ch > max || ch < min) {
            cursor--;
            return true;
        }
        ch -= min;
        if ((s[ch >> 3] & (0X1 << (ch & 0X7))) == 0) {
            cursor--;
            return true;
        }
        return false;
    }

    protected boolean in_range(int min, int max) {
        if (cursor >= limit) {
            return false;
        }
        char ch = current.charAt(cursor);
        if (ch > max || ch < min) {
            return false;
        }
        cursor++;
        return true;
    }

    protected boolean in_range_b(int min, int max) {
        if (cursor <= limit_backward) {
            return false;
        }
        char ch = current.charAt(cursor - 1);
        if (ch > max || ch < min) {
            return false;
        }
        cursor--;
        return true;
    }

    protected boolean out_range(int min, int max) {
        if (cursor >= limit) {
            return false;
        }
        char ch = current.charAt(cursor);
        if (!(ch > max || ch < min)) {
            return false;
        }
        cursor++;
        return true;
    }

    protected boolean out_range_b(int min, int max) {
        if (cursor <= limit_backward) {
            return false;
        }
        char ch = current.charAt(cursor - 1);
        if (!(ch > max || ch < min)) {
            return false;
        }
        cursor--;
        return true;
    }

    protected boolean eq_s(int s_size, String s) {
        if (limit - cursor < s_size) {
            return false;
        }
        int i;
        for (i = 0; i != s_size; i++) {
            if (current.charAt(cursor + i) != s.charAt(i)) {
                return false;
            }
        }
        cursor += s_size;
        return true;
    }

    protected boolean eq_s_b(int s_size, String s) {
        if (cursor - limit_backward < s_size) {
            return false;
        }
        int i;
        for (i = 0; i != s_size; i++) {
            if (current.charAt(cursor - s_size + i) != s.charAt(i)) {
                return false;
            }
        }
        cursor -= s_size;
        return true;
    }

    protected boolean eq_v(CharSequence s) {
        return eq_s(s.length(), s.toString());
    }

    protected boolean eq_v_b(CharSequence s) {
        return eq_s_b(s.length(), s.toString());
    }

    protected int find_among(Among v[], int v_size) {
        int i = 0;
        int j = v_size;

        int c = cursor;
        int l = limit;

        int common_i = 0;
        int common_j = 0;

        boolean first_key_inspected = false;

        while (true) {
            int k = i + ((j - i) >> 1);
            int diff = 0;
            int common = common_i < common_j ? common_i : common_j; // smaller
            Among w = v[k];
            int i2;
            for (i2 = common; i2 < w.s_size; i2++) {
                if (c + common == l) {
                    diff = -1;
                    break;
                }
                diff = current.charAt(c + common) - w.s[i2];
                if (diff != 0) {
                    break;
                }
                common++;
            }
            if (diff < 0) {
                j = k;
                common_j = common;
            } else {
                i = k;
                common_i = common;
            }
            if (j - i <= 1) {
                if (i > 0) {
                    break; // v->s has been inspected
                }
                if (j == i) {
                    break; // only one item in v
                }
                // - but now we need to go round once more to get
                // v->s inspected. This looks messy, but is actually
                // the optimal approach.

                if (first_key_inspected) {
                    break;
                }
                first_key_inspected = true;
            }
        }
        while (true) {
            Among w = v[i];
            if (common_i >= w.s_size) {
                cursor = c + w.s_size;
                if (w.method == null) {
                    return w.result;
                }
                boolean res;
                try {
                    Object resobj = w.method.invoke(w.methodobject,
                            new Object[0]);
                    res = resobj.toString().equals("true");
                } catch (InvocationTargetException e) {
                    res = false;
                    // FIXME - debug message
                } catch (IllegalAccessException e) {
                    res = false;
                    // FIXME - debug message
                }
                cursor = c + w.s_size;
                if (res) {
                    return w.result;
                }
            }
            i = w.substring_i;
            if (i < 0) {
                return 0;
            }
        }
    }

    // find_among_b is for backwards processing. Same comments apply
    protected int find_among_b(Among v[], int v_size) {
        int i = 0;
        int j = v_size;

        int c = cursor;
        int lb = limit_backward;

        int common_i = 0;
        int common_j = 0;

        boolean first_key_inspected = false;

        while (true) {
            int k = i + ((j - i) >> 1);
            int diff = 0;
            int common = common_i < common_j ? common_i : common_j;
            Among w = v[k];
            int i2;
            for (i2 = w.s_size - 1 - common; i2 >= 0; i2--) {
                if (c - common == lb) {
                    diff = -1;
                    break;
                }
                diff = current.charAt(c - 1 - common) - w.s[i2];
                if (diff != 0) {
                    break;
                }
                common++;
            }
            if (diff < 0) {
                j = k;
                common_j = common;
            } else {
                i = k;
                common_i = common;
            }
            if (j - i <= 1) {
                if (i > 0) {
                    break;
                }
                if (j == i) {
                    break;
                }
                if (first_key_inspected) {
                    break;
                }
                first_key_inspected = true;
            }
        }
        while (true) {
            Among w = v[i];
            if (common_i >= w.s_size) {
                cursor = c - w.s_size;
                if (w.method == null) {
                    return w.result;
                }

                boolean res;
                try {
                    Object resobj = w.method.invoke(w.methodobject,
                            new Object[0]);
                    res = resobj.toString().equals("true");
                } catch (InvocationTargetException e) {
                    res = false;
                    // FIXME - debug message
                } catch (IllegalAccessException e) {
                    res = false;
                    // FIXME - debug message
                }
                cursor = c - w.s_size;
                if (res) {
                    return w.result;
                }
            }
            i = w.substring_i;
            if (i < 0) {
                return 0;
            }
        }
    }

    /* to replace chars between c_bra and c_ket in current by the
     * chars in s.
     */
    protected int replace_s(int c_bra, int c_ket, String s) {
        int adjustment = s.length() - (c_ket - c_bra);
        current.replace(c_bra, c_ket, s);
        limit += adjustment;
        if (cursor >= c_ket) {
            cursor += adjustment;
        } else if (cursor > c_bra) {
            cursor = c_bra;
        }
        return adjustment;
    }

    protected void slice_check() {
        if (bra < 0
                || bra > ket
                || ket > limit
                || limit > current.length()) // this line could be removed
        {
            System.err.println("faulty slice operation");
            // FIXME: report error somehow.
	/*
             fprintf(stderr, "faulty slice operation:\n");
             debug(z, -1, 0);
             exit(1);
             */
        }
    }

    protected void slice_from(String s) {
        slice_check();
        replace_s(bra, ket, s);
    }

    protected void slice_from(CharSequence s) {
        slice_from(s.toString());
    }

    protected void slice_del() {
        slice_from("");
    }

    protected void insert(int c_bra, int c_ket, String s) {
        int adjustment = replace_s(c_bra, c_ket, s);
        if (c_bra <= bra) {
            bra += adjustment;
        }
        if (c_bra <= ket) {
            ket += adjustment;
        }
    }

    protected void insert(int c_bra, int c_ket, CharSequence s) {
        insert(c_bra, c_ket, s.toString());
    }

    /* Copy the slice into the supplied StringBuffer */
    protected StringBuffer slice_to(StringBuffer s) {
        slice_check();
        int len = ket - bra;
        s.replace(0, s.length(), current.substring(bra, ket));
        return s;
    }

    /* Copy the slice into the supplied StringBuilder */
    protected StringBuilder slice_to(StringBuilder s) {
        slice_check();
        int len = ket - bra;
        s.replace(0, s.length(), current.substring(bra, ket));
        return s;
    }

    protected StringBuffer assign_to(StringBuffer s) {
        s.replace(0, s.length(), current.substring(0, limit));
        return s;
    }

    protected StringBuilder assign_to(StringBuilder s) {
        s.replace(0, s.length(), current.substring(0, limit));
        return s;
    }

    /*
     extern void debug(struct SN_env * z, int number, int line_count)
     {   int i;
     int limit = SIZE(z->p);
     //if (number >= 0) printf("%3d (line %4d): '", number, line_count);
     if (number >= 0) printf("%3d (line %4d): [%d]'", number, line_count,limit);
     for (i = 0; i <= limit; i++)
     {   if (z->lb == i) printf("{");
     if (z->bra == i) printf("[");
     if (z->c == i) printf("|");
     if (z->ket == i) printf("]");
     if (z->l == i) printf("}");
     if (i < limit)
     {   int ch = z->p[i];
     if (ch == 0) ch = '#';
     printf("%c", ch);
     }
     }
     printf("'\n");
     }
     */
};
